package repeater

import (
    "github.com/syndtr/goleveldb/leveldb"
    "github.com/syndtr/goleveldb/leveldb/util"
    "time"
    "fmt"
    "os"
)

type LevelDB struct {
    workingFileName string
    workingDB       *leveldb.DB
    workingData     map[string]DataSet
    workingOffset   map[string]int64
    desc            *DBMeta
}

func (db *LevelDB) Prepare(ip string, offset int64, m ReportMetric, v float64) error {
    if err := db.desc.handleAdd(ip, offset); err != nil {
        return err
    }
    if _, ok := db.workingData[ip]; !ok {
        db.workingData[ip] = make(map[ReportMetric]Value)
    }
    db.workingData[ip].Add(m, v)
    if db.workingOffset[ip] < offset {
        db.workingOffset[ip] = offset
    }
    return nil
}

func (db *LevelDB) ShouldClose(limit int64) bool {
    if db.workingDB == nil {
        return false
    }
    if db.desc.LastWriteTime == 0 {
        return db.desc.InitialTime-db.desc.LastWriteTime > limit
    }
    return time.Now().Unix()-db.desc.LastWriteTime > limit
}

func (db *LevelDB) Commit() error {
    if db.workingDB == nil {
        if len(db.workingData) != 0 || len(db.workingOffset) != 0 {
            return fmt.Errorf("closed db with cached value")
        }
        return nil
    }
    batch := new(leveldb.Batch)
    for ip, value := range db.workingData {
        key := fmt.Sprintf("%s.%d", ip, db.workingOffset[ip])
        k := []byte(key)
        v, err := value.toBytes()
        if err != nil {
            return err
        }
        batch.Put(k, v)
    }
    k, v, err := db.desc.KeyValue()
    if err != nil {
        return err
    }
    batch.Put(k, v)
    if err := db.workingDB.Write(batch, nil); err != nil {
        return err
    }
    db.workingData = make(map[string]DataSet)
    db.workingOffset = make(map[string]int64)
    return nil
}

func (db *LevelDB) Range() ([]string, error) {
    if db.workingDB == nil {
        if err := db.Init(db.workingFileName); err != nil {
            return nil, err
        }
    }
    var ret []string
    for s, flag := range db.desc.Ip {
        if flag {
            ret = append(ret, s)
        }
    }
    return ret, nil
}

func (db *LevelDB) Drop() error {
    log.Info("drop storage %s", db.workingFileName)
    if db.workingDB != nil {
        if err := db.Close(); err != nil {
            return err
        }
    }
    return os.RemoveAll(db.workingFileName)
}
func (db *LevelDB) ShouldDrop(limit int64) bool {
    i := 0
    for _, v := range db.desc.Ip {
        if v == false {
            i++
        }
    }
    return i == len(db.desc.Ip) && db.desc.LastActiveTime-time.Now().Unix() > limit
}

func (db *LevelDB) Clean(ip string) error {
    db.desc.LastActiveTime = time.Now().Unix()
    return db.desc.handleClean(ip)
}

func (db *LevelDB) ValuesFor(ip string) (DataSet, error) {
    var ret []DataSet
    if db.workingDB == nil {
        if err := db.Init(db.workingFileName); err != nil {
            return nil, err
        }
    }
    ret = append(ret, db.workingData[ip])
    prefix := util.BytesPrefix([]byte(ip + " "))
    iter := db.workingDB.NewIterator(prefix, nil)
    defer iter.Release()
    for iter.Next() {
        b := iter.Value()
        ds, err := loadDataSet(b)
        if err != nil {
            return nil, err
        }
        ret = append(ret, ds)
    }
    return joinDataSet(ret), iter.Error()
}

func (db *LevelDB) Close() error {
    k, v, err := db.desc.KeyValue()
    if err != nil {
        return err
    }
    err = db.workingDB.Put(k, v, nil)
    if err != nil {
        return err
    }
    err = db.workingDB.Close()
    if err != nil {
        return err
    }
    db.workingDB = nil
    return nil
}

func (db *LevelDB) Init(fn string) (err error) {
    if db.workingDB != nil {
        return fmt.Errorf("unfinished level %v", db.workingDB)
    }
    if db.workingDB, err = leveldb.OpenFile(fn, nil); err != nil {
        return err
    }
    if b, err := db.workingDB.Get([]byte(DESCNAME), nil); err == nil {
        db.desc = NewDBMeta(b, 0)

    }
    db.workingData = make(map[string]DataSet)
    db.workingOffset = make(map[string]int64)
    db.workingFileName = fn
    return nil
}
